package src.model.gameWorld;

import src.model.gameObject.*;
import src.model.gameObject.ability.AbilityType;
import src.model.gameObject.ability.AbilityAttributes;

import java.io.*;
import java.util.Map;
import java.util.HashMap;
import java.util.StringTokenizer;

/**
 * <b>Overview</b><br>
 * <p>The LookupTable is the object that holds the default 
 * statistics for all abilites and GameObject types in the game.  
 * Upon construction, it loads this data from a file, and stores it 
 * in a heirchy.
 * <br><br>
 * Each individual Ablity/Stat combination has a default statistic
 * value which can be returned.
 * <br>
 * Each GameObject Type/Subtype pairing has a value of its own for 
 * each Ability/Stat paring that that Type/Subtype of game object 
 * is expected to have.  When this value is queried, the value returned 
 * is the sum of the default Ability/Stat value and the Type/Subtype 
 * modifier.
 * <br><br>
 * If a Type/Subtype/Ability/Stat combination is queried where none 
 * exists, an entry is created with a modifier of zero, thereby 
 * inheriting the value from the Ability/Stat paring.  If a 
 * Ability/Stat pairing is queried where none exists, a default 
 * Ability/Stat pairing is created with a value of zero.</p>
 * <br><br>
 * <b>Responsibilites</b><br>
 * <p>The Lookup table is responsible for holding the default 
 * statistic values for all GameObjects, and producing them when 
 * queried.</p>
 * <br><br>
 * <b>Collaborators</b>
 * <br><br>
 * <p>None.</p>
 * <b>Implementor</b> - Brent<br>
 * <b>Tester</b> - Tristan<br>
 * 
 * @author Brent
 *
 */
public class LookupTable
{
	private Map<AbilityPairing, Float> abilityPairings;
	private Map<GameObjectPairing, Float> gameObjectPairings;
	
	
	public LookupTable(String filename)
	{
		abilityPairings = new HashMap<AbilityPairing, Float>();
		gameObjectPairings = new HashMap<GameObjectPairing, Float>();
		
		// we are going to try read the file and create the lookup table
		try
		{
			FileReader inFile = new FileReader(filename);
			BufferedReader inBuff = new BufferedReader(inFile);
			
			String inputSection = "";
			String inputAbility = "";
			String inputObjectType = "";
			String inputObjectSubtype = "";
			String inputStat = "";
			
			String lineIn;
			
			
			while((lineIn = getNextLine(inBuff)) != null)
			{
				
				StringTokenizer line = new StringTokenizer(lineIn);
				String keyword = line.nextToken().toUpperCase();
				
				if(keyword.equals("SECTION"))
				{
					inputSection = line.nextToken();
				}
				else if (keyword.equals("ENDSECTION"))
				{
					inputSection = "";
					inputObjectType = "";
					inputObjectSubtype = "";
				}
				else if (keyword.equals("OBJECT"))
				{
					inputObjectType = line.nextToken();
					inputObjectSubtype = line.nextToken();
				}
				else if (keyword.equals("ENDOBJECT"))
				{
					inputObjectType = "";
					inputObjectSubtype = "";
				}
				else if (keyword.equals("ABILITY"))
				{
					inputAbility = line.nextToken();
				}
				else if (keyword.equals("STAT"))
				{
					inputStat = line.nextToken();
					
					if(inputSection.equals("ABILITY-STAT"))
					{
						addEntry(inputAbility, inputStat, line.nextToken());
					}
					else if (inputSection.equals("OBJECT-STAT"))
					{
						addObjectEntry(inputObjectType, inputObjectSubtype, inputAbility, inputStat, line.nextToken());
					}
							
				}
			}
			
		} catch (IOException e)
		{
			System.err.println("ERROR READING DATA FILE - LOOKUP TABLE SET TO ZERO DEFAULT");
		}		
	}
	
	public float statQuery(AbilityType ability, AbilityAttributes stat)
	{
		AbilityPairing ap = new AbilityPairing(ability, stat);
		
		if(!abilityPairings.containsKey(ap))
			abilityPairings.put(ap, 0.0f);
		
		return abilityPairings.get(ap);
	}
	
	public float statQuery(GameObjectType gameObjectType, GameObjectSubtype 
			gameObjectSubtype, AbilityType ability, AbilityAttributes stat)
	{
		GameObjectPairing gp = new GameObjectPairing(
				gameObjectType,
				gameObjectSubtype,
				ability,
				stat);
		
		if(!gameObjectPairings.containsKey(gp))
			gameObjectPairings.put(gp, 0.0f);
		
		return statQuery(ability, stat) + gameObjectPairings.get(gp);
	}
	
	public float setStat(AbilityType ability, AbilityAttributes stat, float amount)
	{
		AbilityPairing ap = new AbilityPairing(ability, stat);
		
		abilityPairings.put(ap, amount);
		
		return statQuery(ability, stat);
	}
	
	public float setStat(GameObjectType gameObjectType, GameObjectSubtype gameObjectSubtype,
			AbilityType ability, AbilityAttributes stat, float amount)
	{
		GameObjectPairing gp = new GameObjectPairing(
				gameObjectType,
				gameObjectSubtype,
				ability,
				stat);
		
		amount -= statQuery(ability, stat);
		gameObjectPairings.put(gp, amount);
		
		return statQuery(gameObjectType, gameObjectSubtype, ability, stat);
	}
	
	public float modifyStat(AbilityType ability, AbilityAttributes stat, float amount)
	{
		setStat(ability, stat, statQuery(ability, stat) + amount);
		
		return statQuery(ability, stat);
	}
	
	public float modifyStat(GameObjectType gameObjectType, GameObjectSubtype gameObjectSubtype,
			AbilityType ability, AbilityAttributes stat, float amount)
	{
		setStat(gameObjectType, gameObjectSubtype, ability, stat, 
				statQuery(gameObjectType, gameObjectSubtype, ability, stat) + amount);
		
		return statQuery(gameObjectType, gameObjectSubtype, ability, stat);	
	}
	
	private String getNextLine(BufferedReader inBuff) throws IOException
	{
		String line;
		
		do
		{
			 line = inBuff.readLine();
		} while (line != null && (line.trim().equals("") || line.trim().startsWith("#")));
		
		return line;
	}
	
	private void addEntry(String ability, String stat, String value)
	{
		AbilityPairing ap = new AbilityPairing(
				AbilityType.valueOf(ability),
				AbilityAttributes.valueOf(stat));
		
		abilityPairings.put(ap, Float.parseFloat(value));
		
	}
	
	private void addObjectEntry(String type, String subtype, String ability, String stat, String value)
	{
		GameObjectPairing gp = new GameObjectPairing(
				GameObjectType.valueOf(type),
				GameObjectSubtype.valueOf(subtype),
				AbilityType.valueOf(ability), 
				AbilityAttributes.valueOf(stat));
		
		AbilityPairing ap = new AbilityPairing(
				AbilityType.valueOf(ability), 
				AbilityAttributes.valueOf(stat));
		
		if(!abilityPairings.containsKey(ap))
			abilityPairings.put(ap, 0.0f);
				
		float amount = Float.parseFloat(value);
		
		amount -= abilityPairings.get(ap);
		gameObjectPairings.put(gp, amount);
	}
	
	protected class AbilityPairing
	{
		public AbilityType abilityType;
		public AbilityAttributes abilityAttribute;
		
		public AbilityPairing(AbilityType at, AbilityAttributes aa)
		{
			abilityType = at;
			abilityAttribute = aa;
		}
		
		public boolean equals(Object o)
		{
			AbilityPairing ap = (AbilityPairing)o;
			return (this.abilityType == ap.abilityType) && (this.abilityAttribute == ap.abilityAttribute);
		}
		
		public int hashCode()
		{
			int code = 0;
			code += abilityType.ordinal();
			code <<= 15;
			code += abilityAttribute.ordinal();
			
			return code;
		}
	}
	
	protected class GameObjectPairing
	{
		public GameObjectType objectType;
		public GameObjectSubtype objectSubtype;
		public AbilityType abilityType;
		public AbilityAttributes abilityAttribute;
		
		public GameObjectPairing(GameObjectType got, GameObjectSubtype gos, AbilityType at, AbilityAttributes aa)
		{
			objectType = got;
			objectSubtype = gos;
			abilityType = at;
			abilityAttribute = aa;
		}
		
		public boolean equals(Object o)
		{
			GameObjectPairing gp = (GameObjectPairing)o;
			return (this.objectType == gp.objectType)
				&& (this.objectSubtype == gp.objectSubtype)
				&& (this.abilityType == gp.abilityType)
				&& (this.abilityAttribute == gp.abilityAttribute);
		}
		
		public int hashCode()
		{
			int code = 0;
			code += objectType.ordinal();
			code <<= 7;
			code += objectSubtype.ordinal();
			code <<= 7;
			code += abilityType.ordinal();
			code <<= 7;
			code += abilityAttribute.ordinal();
			
			return code;
		}
	}
	
	public static void main(String[] args)
	{
		LookupTable lt = new LookupTable("data/model/lookupTableStats.lts");
		
		lt.modifyStat(GameObjectType.UNIT,
						GameObjectSubtype.GALLEON,
						AbilityType.MOVE,
						AbilityAttributes.MAXIMUM,
						1.0f);
			
		System.out.println(
						lt.statQuery(
						GameObjectType.UNIT,
						GameObjectSubtype.GALLEON,
						AbilityType.MOVE,
						AbilityAttributes.MAXIMUM
				));
		
	}
}
